package org.tech4j.springboot.starter.edi.common;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.tech4j.edi.api.RetryExecutor;
import org.tech4j.edi.api.RetryExecutorChain;
import org.tech4j.edi.api.RetryRecordService;
import org.tech4j.edi.api.context.RetryContext;
import org.tech4j.edi.api.context.RetryContextService;
import org.tech4j.edi.base.RetryRecordExecutorService;
import org.tech4j.edi.base.config.RetryPoolConfig;
import org.tech4j.edi.base.executor.ActuallyDoRetryExecutor;
import org.tech4j.edi.base.executor.DynamicThreadPoolRetryExecutor;
import org.tech4j.edi.base.executor.FilterRetryExecutor;
import org.tech4j.edi.base.executor.RetryContextRetryExecutor;
import org.tech4j.edi.base.retry.DefaultRetryContextService;
import org.tech4j.edi.persistence.api.RetryConfigPersistService;
import org.tech4j.edi.persistence.api.RetryRecordPersistService;
import org.tech4j.springboot.starter.edi.persistence.common.EdiPersistenceConfiguration;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;

/**
 * @program: tech4j
 * @description:
 * @author: YuKai Fan
 * @create: 2025/3/10 10:54
 **/
@Configuration
@AutoConfigureAfter(EdiPersistenceConfiguration.class)
public class RetryCommonConfiguration {

    private static final Logger log = LoggerFactory.getLogger(RetryCommonConfiguration.class);

    @Bean
    public RetryRecordService retryRecordService(
            final ObjectProvider<List<RetryExecutor>> executorsProvider,
            final ObjectProvider<RetryRecordPersistService> recordProvider,
            final ObjectProvider<RetryConfigPersistService> configProvider) {
        List<RetryExecutor> retryExecutorList = executorsProvider.getIfAvailable(Collections::emptyList);
        List<RetryExecutor> sortedExecutors = retryExecutorList.stream()
                .sorted(Comparator.comparingInt(RetryExecutor::getOrder)).toList();
        sortedExecutors.forEach(retryExecutor -> log.info("load RetryExecutor:[{}] [{}]", retryExecutor.getClass().getName(), retryExecutor.getOrder()));
        return new RetryRecordExecutorService(
                new DefaultRetryExecutorChain(sortedExecutors),
                Objects.requireNonNull(configProvider.getIfAvailable()),
                Objects.requireNonNull(recordProvider.getIfAvailable())
        );
    }

    @Bean
    public RetryExecutor retryContextRetryExecutor() {
        return new RetryContextRetryExecutor();
    }

    @Bean
    public RetryExecutor filterRetryExecutor() {
        return new FilterRetryExecutor();
    }

    @Bean
    public RetryExecutor dynamicThreadPoolRetryExecutor() {
        return new DynamicThreadPoolRetryExecutor();
    }

    @Bean
    public RetryExecutor actuallyDoRetryExecutor(final RetryContextService retryContextService) {
        return new ActuallyDoRetryExecutor(retryContextService);
    }

    @Bean
    @ConditionalOnMissingBean(RetryContextService.class)
    public RetryContextService retryContextService(final ObjectProvider<RetryRecordPersistService> provider) {
        return new DefaultRetryContextService(Objects.requireNonNull(provider.getIfAvailable()));
    }

    private static class DefaultRetryExecutorChain implements RetryExecutorChain {

        private int index;
        private final List<RetryExecutor> executors;

        public DefaultRetryExecutorChain(final List<RetryExecutor> executors) {
            this.executors = executors;
        }

        @Override
        public void execute(RetryContext retryContext) {
            if (this.index < executors.size()) {
                RetryExecutor retryExecutor = executors.get(this.index++);
                boolean skip = retryExecutor.skip(retryContext);
                if (skip) {
                    this.execute(retryContext);
                } else {
                    retryExecutor.execute(retryContext, this);
                }
            }
        }
    }
}
